
#include <BFont>
#include <BPoint>
#include <BPainter>
#include <BMouseEvent>
#include <BStyle>

#include <BTabWidget>
#include "member_BTabWidget.h"
#include "member_BAction.h"
#include "member_BWidget.h"
#include "member_BStyle.h"

using namespace BWE;

#define member					(*(member_BTabWidget*)_ptr)
#define member_allocate()		_ptr = new member_BTabWidget(this)
#define member_release()		delete (member_BTabWidget*)_ptr

BTabWidget::BTabWidget(BWidget* parent) : BWidget(parent)
{
	member_allocate();
	this->setBorder(1);
	this->setMargin(1, 1, 1, 1);
}
BTabWidget::BTabWidget(const BString& name, BWidget* parent) : BWidget(name, parent)
{
	member_allocate();
	this->setBorder(1);
	this->setMargin(1, 1, 1, 1);
}
BTabWidget::~BTabWidget()
{
	for (TabItem& item : member.items)
	{
		member.disconnect(item.widget);
	}
	member_release();
}

void BTabWidget::setTabPart(PartFlag tabPart)
{
	if (member.tabPart != tabPart)
	{
		member.tabPart = tabPart;
		member.dirty = true;
		this->fresh();
	}
}
PartFlag BTabWidget::tabPart() const
{
	return member.tabPart;
}

bool BTabWidget::setTabVisible(bool tabVisible)
{
	if (member.tabVisible != tabVisible)
	{
		member.tabVisible = tabVisible;
		member.dirty = true;
		this->fresh();
		return true;
	}
	return false;
}
bool BTabWidget::tabVisible() const
{
	return member.tabVisible;
}

bool BTabWidget::setTabSpacing(int tabSpacing)
{
	if (member.tabSpacing != tabSpacing)
	{
		member.tabSpacing = tabSpacing;
		member.dirty = true;
		this->fresh();
		return true;
	}
	return false;
}
int BTabWidget::tabSpacing() const
{
	return member.tabSpacing;
}

bool BTabWidget::setTabMargin(const BMargin& tabMargin)
{
	if (member.tabMargin != tabMargin)
	{
		member.tabMargin = tabMargin;
		member.dirty = true;
		this->fresh();
		return true;
	}
	return false;
}
const BMargin& BTabWidget::tabMargin() const
{
	return member.tabMargin;
}

bool BTabWidget::setArrowVisible(bool arrowVisible)
{
	if (member.arrowVisible != arrowVisible)
	{
		member.arrowVisible = arrowVisible;
		member.dirty = true;
		this->fresh();
		return true;
	}
	return false;
}
bool BTabWidget::arrowVisible() const
{
	return member.arrowVisible;
}

void BTabWidget::setArrowSize(const BSize& arrowSize)
{
	if (member.arrowSize != arrowSize)
	{
		member.arrowSize = arrowSize;
		this->fresh();
	}
}
const BSize& BTabWidget::arrowSize() const
{
	return member.arrowSize;
}

void BTabWidget::setArrowAlign(Align arrowAlign)
{
	if (member.arrowAlign != arrowAlign)
	{
		member.arrowAlign = arrowAlign;
		this->fresh();
	}
}
Align BTabWidget::arrowAlign() const
{
	return member.arrowAlign;
}

bool BTabWidget::addTab(BWidget* widget, bool closable)
{
	if (!widget)
		return false;
	if (member.items.contain(widget))
		return true;
	this->addChild(widget);
	member.items.append(widget);
	member.connect(widget, Signal_Show, &member_BTabWidget::slotWidgetShow);
	member.connect(widget, Signal_Hide, &member_BTabWidget::slotWidgetHide);
	member.connect(widget, Signal_IconChanged, &member_BTabWidget::slotWidgetIconChanged);
	member.connect(widget, Signal_TitleChanged, &member_BTabWidget::slotWidgetTitleChanged);
	setTabClosable(widget, closable);
	if (member.stack.empty())
	{
		this->setCurrent(widget);
	}
	else
	{
		member.stack.prepend(widget);
		widget->hide();
	}
	member.dirty = true;
	this->fresh();
	return true;
}
bool BTabWidget::insertTab(int pos, BWidget* widget, bool closable)
{
	if (!widget)
		return false;
	if (member.items.contain(widget))
	{
		moveTab(widget, pos);
		setTabClosable(widget, closable);
		return true;
	}
	this->addChild(widget);
	member.items.insert(pos, widget);
	member.connect(widget, Signal_Show, &member_BTabWidget::slotWidgetShow);
	member.connect(widget, Signal_Hide, &member_BTabWidget::slotWidgetHide);
	member.connect(widget, Signal_IconChanged, &member_BTabWidget::slotWidgetIconChanged);
	member.connect(widget, Signal_TitleChanged, &member_BTabWidget::slotWidgetTitleChanged);
	setTabClosable(widget, closable);
	member.stack.prepend(widget);
	if (member.stack.empty())
	{
		this->setCurrent(widget);
	}
	else
	{
		member.stack.prepend(widget);
		widget->hide();
	}
	member.dirty = true;
	this->fresh();
	return true;
}
bool BTabWidget::removeTab(BWidget* widget)
{
	if (!widget)
		return false;
	auto it = member.items.find(widget);
	if (it.valid())
	{
		BHolder<BWidget> holder(widget);
		setTabClosable(widget, false);
		member.disconnect(widget);
		member.items.erase(it);
		this->removeChild(widget);
		if (member.stack.last() == widget)
		{
			member.stack -= widget;
			BWidget* current = member.stack.last();
			if (current)
			{
				current->show();
				current->setRect(this->centerRect());
			}
			this->emit(Signal_CurrentChanged, current);
		}
		else
		{
			member.stack.remove(widget);
		}
		member.dirty = true;
		emit(Signal_TabClosed, widget);
		this->fresh();
		return true;
	}
	return false;
}

bool BTabWidget::setTabClosable(BWidget* widget, bool closable)
{
	auto it = member.items.find(widget);
	if (it.empty())
		return false;
	TabItem& item = *it;
	bool itemClosable = item.closeButton != 0;
	if (itemClosable != closable)
	{
		if (closable)
		{
			item.closeButton = new BButton(Graph_Cross, this);
			item.closeButton->setName("close");
			member.connect(item.closeButton, Signal_Clicked, &member_BTabWidget::slotCloseButtonClicked);
			member.connect(item.closeButton, Signal_Styled, &member_BTabWidget::slotCloseButtonStyled);
			item.dirty = true;
		}
		else
		{
			member.disconnect(item.closeButton);
			if (!member.board->removeChild(item.closeButton))
				this->removeChild(item.closeButton);
			item.closeButton = 0;
			item.dirty = true;
		}
		member.dirty = true;
		this->fresh();
	}
	return true;
}
bool BTabWidget::tabClosable(BWidget* widget) const
{
	auto it = member.items.find(widget);
	if (it.empty())
		return false;
	TabItem& item = *it;
	return item.closeButton != 0;
}

bool BTabWidget::setTabAsset(BWidget* widget, const BValue& asset)
{
	auto it = member.items.find(widget);
	if (it.empty())
		return false;
	TabItem& item = *it;
	if (item.asset != asset)
	{
		item.asset = asset;
		this->fresh();
		return true;
	}
	return false;
}
const BValue& BTabWidget::tabAsset(const BWidget* widget) const
{
	auto it = member.items.find(widget);
	return it->asset;
}

bool BTabWidget::setTabTips(const BWidget* widget, const BString& tabTips)
{
	auto it = member.items.find(widget);
	if (it.empty())
		return false;
	TabItem& item = *it;
	if (item.tips != tabTips)
	{
		item.tips = tabTips;
		this->fresh();
		return true;
	}
	return false;
}
const BString& BTabWidget::tabTips(const BWidget* widget) const
{
	auto it = member.items.find(widget);
	return it->tips;
}

bool BTabWidget::removeTab(const BString& title)
{
	BWidget* widget = this->tab(title);
	return removeTab(widget);
}
bool BTabWidget::removeTab(int tab)
{
	BWidget* widget = member.items(tab).widget;
	return removeTab(widget);
}

bool BTabWidget::containTab(const BWidget* widget) const
{
	return member.items.contain(widget);
}
bool BTabWidget::containTab(const BString& title) const
{
	for (const auto& item : member.items)
		if (item.widget->title() == title)
			return true;
	return false;
}

bool BTabWidget::moveTab(BWidget* widget, int index)
{
	if (member.items.check(index))
	{
		auto from = member.items.find(widget);
		auto dest = member.items.seek(index);
		if (member.items.move(from, dest))
		{
			member.dirty = true;
			this->fresh();
			return true;
		}
	}
	return false;
}
int BTabWidget::tabIndex(BWidget* widget) const
{
	return member.items.index(widget);
}

bool BTabWidget::clearTabs()
{
	if (member.items.empty())
		return false;
	while (member.items.size())
	{
		removeTab(0);
	}
	this->fresh();
	return true;
}

bool BTabWidget::setCurrentIndex(int index)
{
	BWidget* widget = member.items(index).widget;
	return setCurrent(widget);
}
int BTabWidget::currentIndex() const
{
	BWidget* widget = member.stack.last();
	return member.items.index(widget);
}

bool BTabWidget::setCurrent(const BString& title)
{
	for (auto item : member.items)
	{
		if (item.widget->title() == title)
		{
			return setCurrent(item.widget);
		}
	}
	return false;
}
bool BTabWidget::setCurrent(BWidget* widget)
{
	if (member.stack.last() != widget)
	{
		BWidget* current = member.stack.last();
		if (current)
			current->hide();
		if (widget)
		{
			widget->show();
			widget->setRect(this->centerRect());
			if (!member.stack.lower(widget))
				member.stack.append(widget);
		}
		emit(Signal_CurrentChanged, widget);
		this->fresh();
		return true;
	}
	return false;
}
BWidget* BTabWidget::current() const
{
	return member.stack.last();
}

int BTabWidget::tabCount() const
{
	return member.items.size();
}
BWidget* BTabWidget::tab(int index)
{
	return member.items(index).widget;
}
const BWidget* BTabWidget::tab(int index) const
{
	return member.items(index).widget;
}

BWidget* BTabWidget::tab(const BString& title)
{
	for (int i = 0; i < member.items.size(); i++)
	{
		const TabItem& item = member.items[i];
		if (item.widget->title() == title)
			return item.widget;
	}
	return 0;
}
const BWidget* BTabWidget::tab(const BString& title) const
{
	for (int i = 0; i < member.items.size(); i++)
	{
		const TabItem& item = member.items[i];
		if (item.widget->name() == title)
			return item.widget;
	}
	return 0;
}

BWidget* BTabWidget::namedTab(const BString& name)
{
	for (int i = 0; i < member.items.size(); i++)
	{
		const TabItem& item = member.items[i];
		if (item.widget->name() == name)
			return item.widget;
	}
	return 0;
}
const BWidget* BTabWidget::namedTab(const BString& name) const
{
	for (int i = 0; i < member.items.size(); i++)
	{
		const TabItem& item = member.items[i];
		if (item.widget->name() == name)
			return item.widget;
	}
	return 0;
}

BWidget* BTabWidget::tabOfAsset(const BValue& asset)
{
	for (int i = 0; i < member.items.size(); i++)
	{
		const TabItem& item = member.items[i];
		if (item.asset == asset)
			return item.widget;
	}
	return 0;
}
const BWidget* BTabWidget::tabOfAsset(const BValue& asset) const
{
	for (int i = 0; i < member.items.size(); i++)
	{
		const TabItem& item = member.items[i];
		if (item.widget->asset() == asset)
			return item.widget;
	}
	return 0;
}

BRect BTabWidget::tabRect(int index) const
{
	if (member.dirty)
	{
		member.freshTabItems();
		member.dirty = false;
	}
	return member.items(index).rect();
}
BRect BTabWidget::tabRect(const BWidget* widget) const
{
	if (member.dirty)
	{
		member.freshTabItems();
		member.dirty = false;
	}
	return member.rectOf(widget);
}

const BIcon* BTabWidget::tabIcon(int index) const
{
	if (member.dirty)
	{
		member.freshTabItems();
		member.dirty = false;
	}
	return member.items(index).icon;
}
const BIcon* BTabWidget::tabIcon(const BWidget* widget) const
{
	if (member.dirty)
	{
		member.freshTabItems();
		member.dirty = false;
	}
	int index = member.indexOf(widget);
	return member.items(index).icon;
}

const BString& BTabWidget::tabText(int index) const
{
	if (member.dirty)
	{
		member.freshTabItems();
		member.dirty = false;
	}
	return member.items(index).block.text;
}
const BString& BTabWidget::tabText(const BWidget* widget) const
{
	if (member.dirty)
	{
		member.freshTabItems();
		member.dirty = false;
	}
	int index = member.indexOf(widget);
	return member.items(index).block.text;
}

const BString& BTabWidget::realTips() const
{
	if (member.hoverIndex >= 0)
	{
		TabItem& item = member.items.at(member.hoverIndex);
		if (item.tips.size())
			return item.tips;
	}
	return BWidget::realTips();
}

BSize BTabWidget::sizeHint() const
{
	BSize contentSize;
	for (int i = 0; i < member.items.size(); i++)
	{
		BSize sizeHint = member.items[i].widget->sizeHint();
		if (contentSize.width() < sizeHint.width())
			contentSize.width() = sizeHint.width();
		if (contentSize.height() < sizeHint.height())
			contentSize.height() = sizeHint.height();
	}
	return this->preferSize(contentSize);
}

bool BTabWidget::input(const BString& text, const BValue& value)
{
	if (text == "current")
	{
		BWidget* widget = value;
		setCurrent(widget);
		return true;
	}
	return BWidget::input(text, value);
}
bool BTabWidget::query(const BString& text, BValue& value) const
{
	if (text == "current")
	{
		value = member.stack.last();
		return true;
	}
	if (text == "stack")
	{
		BWidgetArray& widgets = value.fill<BWidgetArray>();
		widgets = member.stack;
		return true;
	}
	if (text == "all-tabs")
	{
		BWidgetArray& widgets = value.fill<BWidgetArray>();
		widgets.reset();
		for (int i = 0; i < member.items.size(); i++)
		{
			TabItem& item = member.items[i];
			widgets.append(item.widget);
		}
	}
	return BWidget::query(text, value);
}

void BTabWidget::resizeEvent(const BEvent& event)
{
	if (BWidget* current = this->current())
	{
		current->setRect(this->centerRect());
	}
	member.dirty = true;
	BWidget::resizeEvent(event);
}
void BTabWidget::updateEvent(const BEvent& event)
{
	if (member.tabClosing)
	{
		this->removeTab(member.tabClosing);
		member.tabClosing = 0;
	}
	BWidget::updateEvent(event);
}

void BTabWidget::styleEvent(const BEvent& event)
{
	BWidget::styleEvent(event);
	const BStyle* style = this->realStyle();

	member.autoTop = style->value(Value_Perch_Top).empty();
	member.autoLeft = style->value(Value_Perch_Left).empty();
	member.autoRight = style->value(Value_Perch_Right).empty();
	member.autoBottom = style->value(Value_Perch_Bottom).empty();

	style_member(style)->assign(Value_Arrow_Size, member.arrowSize);
	style_member(style)->assign(Value_Arrow_Align, member.arrowAlign);

	style_member(style)->assign(Value_Spacing, member.tabSpacing);

	if (const BStyle* tabStyle = style->annex("tab"))
	{
		member.tabStyle = tabStyle;

		style_member(tabStyle)->assign(Value_Align, member.tabAlign);
		style_member(tabStyle)->assign(Value_Width, member.tabSize.width());
		style_member(tabStyle)->assign(Value_Height, member.tabSize.height());
		style_member(tabStyle)->assign(Value_Spacing, member.tabSpacing);

		style_member(tabStyle)->assign(Value_Margin_Top, member.tabMargin.top());
		style_member(tabStyle)->assign(Value_Margin_Left, member.tabMargin.left());
		style_member(tabStyle)->assign(Value_Margin_Right, member.tabMargin.right());
		style_member(tabStyle)->assign(Value_Margin_Bottom, member.tabMargin.bottom());
	}

	for (int i = 0; i < member.items.size(); i++)
	{
		member.items[i].dirty = true;
	}
	member.dirty = true;
}
void BTabWidget::freshEvent(const BEvent& event)
{
	if (member.dirty)
	{
		member.freshTabItems();
		member.dirty = false;
	}
	BWidget::freshEvent(event);
}
void BTabWidget::paintEvent(const BEvent& event)
{
	BPainter painter(this);
	painter.drawBackground();
	painter.drawPerch();

	State state = painter.state();
	for (int i = 0; i < member.items.size(); i++)
	{
		TabItem& item = member.items[i];
		if (!item.visible)
			continue;
		State itemState = state;
		if (i == member.hoverIndex)
		{
			if (member.pressed)
				itemState |= State_Pressed;
			else
				itemState |= State_Hovered;
		}
		else if (item.widget == member.stack.last())
		{
			itemState |= State_Selected;
		}
		painter.pushMatrix();
		painter.translate(item.pos);
		//painter.setClip(item_member(item)->rect() & crect);
		painter.setStyle(item.realStyle);
		item.drawBackground(painter, itemState);
		if (item.margin.valid())
			item.drawMargin(painter, itemState);
		if (item.border > 0)
			item.drawBorder(painter, itemState);
		item.drawContent(painter, itemState);
		painter.popMatrix();
	}
	painter.setStyle(0);

	painter.drawMargin();
	painter.drawBorder();
}

void BTabWidget::mouseEnterEvent(const BMouseEvent& mouseEvent)
{
	if (member.hoverIndex != -1)
	{
		member.hoverIndex = -1;
		emit(Signal_TabHovered);
		this->fresh();
	}
	BWidget::mouseEnterEvent(mouseEvent);
}
void BTabWidget::mouseLeaveEvent(const BMouseEvent& mouseEvent)
{
	if (member.hoverIndex != -1)
	{
		member.hoverIndex = -1;
		emit(Signal_TabHovered);
		this->fresh();
	}
	BWidget::mouseLeaveEvent(mouseEvent);
}

void BTabWidget::mousePressEvent(const BMouseEvent& mouseEvent)
{
	if (member.hoverIndex >= 0)
	{
		setCurrentIndex(member.hoverIndex);
		this->fresh();
	}
	if (mouseEvent.button() == Button_Left)
	{
		member.pressed = true;
		this->fresh();
	}
	BWidget::mousePressEvent(mouseEvent);
}
void BTabWidget::mouseReleaseEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		member.pressed = false;
		this->fresh();
	}
	BWidget::mouseReleaseEvent(mouseEvent);
}
void BTabWidget::mouseMoveEvent(const BMouseEvent& mouseEvent)
{
	int hoverIndex = -1;
	const BPoint& pos = mouseEvent.pos();
	for (int i = 0; i < member.items.size(); i++)
	{
		TabItem& item = member.items[i];
		if (item.rect().contain(pos))
		{
			hoverIndex = i;
			break;
		}
	}
	if (member.hoverIndex != hoverIndex)
	{
		member.hoverIndex = hoverIndex;
		BWidget* widget = member.items(hoverIndex).widget;
		emit(Signal_TabHovered, widget);
		this->fresh();
	}
	BWidget::mouseMoveEvent(mouseEvent);
}

void BTabWidget::mouseClickEvent(const BMouseEvent& mouseEvent)
{
	if (member.hoverIndex >= 0)
	{
		BWidget* widget = member.items(member.hoverIndex).widget;
		if (mouseEvent.button() == Button_Left)
		{
			emit(Signal_TabClicked, widget);
			return;
		}
		if (mouseEvent.button() == Button_Right)
		{
			emit(Signal_TabRightClicked, widget);
			return;
		}
	}
	BWidget::mouseClickEvent(mouseEvent);
}
